package de.mafr.jsystest.ws;

import java.io.IOException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;

import de.mafr.jsystest.SystemTestConfig;
import de.mafr.jsystest.SystemTestException;



/**
 * Base class for all web services.
 *
 * Note: This implementation uses strings internally instead of streams, so
 * performance is only suitable for test cases.
 *
 * @param <T> the class used for request and response content
 */
public abstract class AbstractWebService<T> implements WebService<T> {
	private HttpClient client;
	private ResponseFactory<T> responseFactory;
	
	private String hostname;
	private int port;


	public AbstractWebService(ResponseFactory<T> responseFactory, SystemTestConfig config) {
		this.hostname = config.getString("webservice.hostname");
		this.port = config.getInt("webservice.port");
		this.client = createHttpClient();
		this.responseFactory = responseFactory;
	}
	

	public Response<T> get(String uri) {
		GetMethod method = new GetMethod(makeUri(uri));
		method.setDoAuthentication(true);
		
		return executeHttpMethod(method);
	}

	public Response<T> post(String uri, Request<T> request) {
		PostMethod method = new PostMethod(makeUri(uri));
		method.setDoAuthentication(true);
		
		RequestEntity requestEntity = HttpUtils.createRequestEntity(request);
		method.setRequestEntity(requestEntity);
		
		return executeHttpMethod(method);
	}

	public Response<T> put(String uri, Request<T> request) {
		PutMethod method = new PutMethod(makeUri(uri));
		method.setDoAuthentication(true);
		
		RequestEntity requestEntity = HttpUtils.createRequestEntity(request);
		method.setRequestEntity(requestEntity);
		
		return executeHttpMethod(method);
	}
	
	public Response<T> delete(String uri) {
		DeleteMethod method = new DeleteMethod(makeUri(uri));
		method.setDoAuthentication(true);
		
		return executeHttpMethod(method);
	}


	public void setCredentials(String realm, String userName, String password) {
        this.client.getState().setCredentials(
                new AuthScope(this.hostname, this.port, realm),
                new UsernamePasswordCredentials(userName, password)
        );
	}
	
	// TODO: make protected in case subclasses want something special here
	private static HttpClient createHttpClient() {
		HttpClientParams params = new HttpClientParams();
		params.setParameter(HttpMethodParams.RETRY_HANDLER,
				new DefaultHttpMethodRetryHandler(0, false));
		
		return new HttpClient(params);
	}
	
	/**
	 * Execute a HTTP method and re-wrap exceptions.
	 */
	private Response<T> executeHttpMethod(HttpMethodBase method) {
		try {
			client.executeMethod(method);

			return responseFactory.create(method);

		} catch (HttpException e) {
			throw new SystemTestException("Fatal protocol violation: " + e.getMessage(), e);
		} catch (IOException e) {
			throw new SystemTestException("Fatal transport error: " + e.getMessage(), e);
		} finally {
            method.releaseConnection();
        }
	}
	
	private String makeUri(String uri) {
		if (! uri.startsWith("http://") && ! uri.startsWith("https://"))
			return "http://" + hostname + ":" + port + uri;
		else
			return uri;
	}
}
